package cn.ckx.coding.common.utils.hash;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 *
 */
class Checksum {

	private static final String CODED_FORMAT = "UTF-8";
	
	private static final Charset CHARSET = Charset.forName(CODED_FORMAT);

	private final Builder builder;

	/**
	 *  * <li>{@code MD5}</li>
	 *  * <li>{@code SHA-1}</li>
	 *  * <li>{@code SHA-256}</li>
	 */
	Checksum(String algorithm) {
		builder = builder(algorithm);
	}

	/**
	 * 把字符串转换为MD5散列数据摘要，通常用于密码验证。
	 *
	 * @param source 需要hash的string资源
	 * @return 密文
	 */
	String hash(String source) {
		return builder.update(source.getBytes(CHARSET)).sum();
	}

	/**
	 * 对字节数组计算校验和，通常用于用于判断文件内容是否相同。
	 *
	 * @param source 需要hash的byte[]资源
	 * @return 密文
	 */
	String sum(byte[] source) {
		return builder.update(source).sum();
	}

	/**
	 * 对输入流里面的内容生成指纹
	 * 注意：流只能被使用一次！
	 *
	 * @param input 输入流
	 * @return 字符串
	 */
	String sum(InputStream input) {
		try {
			byte[] buf = new byte[1024];
			for (int i = input.read(buf); i != -1; i = input.read(buf)) {
				builder.update(buf, 0, i);
			}
			return builder.sum();
		} catch (IOException e) {
			throw new UncheckedIOException("无法计算输入流的指纹", e);
		}
	}

	public static Builder builder(String algorithm) {
		return new Builder(algorithm);
	}

	private static class Builder {
		
		private MessageDigest messageDigest;

		// 构建数据摘要算法
		private Builder(String algorithm) {
			try {
				messageDigest = MessageDigest.getInstance(algorithm);
				messageDigest.reset();
			} catch (NoSuchAlgorithmException e) {
				throw new IllegalArgumentException("不支持的算法" + algorithm + "，" + e.getLocalizedMessage(), e);
			}
		}

		/**
		 * 把字节数组更新到数据摘要里面
		 * 
		 * @param data 字节流
		 * @param offset 数组偏移数量
		 * @param len 要使用的字节数
		 * @return Builder 建造则
		 */
		public Builder update(byte[] data, int offset, int len) {
			messageDigest.update(data, offset, len);
			return this;
		}

		/**
		 * 把字节数组更新到数据摘要里面
		 * 
		 * @param data 字节流
		 * @return Builder
		 */
		public Builder update(byte[] data) {
			update(data, 0, data.length);
			return this;
		}

		/**
		 * 计算数据摘要的结果，转换为十六进制字符串
		 * 
		 * @return String
		 */
		public String sum() {
			byte[] digest = messageDigest.digest();
			return ByteUtils.toHex(digest);
		}
	}
}
